using System;
using System.Linq;
using System.Linq.Expressions;
using Magiq.Tests.Support;
using Magiq.Tests.TestModel;
using NUnit.Framework;

namespace Magiq.Tests
{
    [TestFixture]
    public abstract class BaseMagiqProviderBulkInsertsTest<TTestClass> : BaseMagiqProviderTest<TTestClass> where TTestClass : class, ITestClass<TTestClass>, new()
    {
        protected IQueryable<TTestClass> Destination { get; private set; }

        protected override void Initialize()
        {
            Destination = Configurator.GetQueryable<TTestClass>();
        }

        protected void TestInsert<T>(Expression<Func<T, bool>> where, Expression<Func<T, T>> insert,
                                   IQueryable<T> destination)
            where T : class, new()
        {
            var count = Configurator.GetQueryable<T>().Where(where).Count();
            var prevDestinationCount = destination.Count();
            destination.BulkInsert(Configurator.GetQueryable<T>().Where(where).Select(insert)).Should(Be.EqualTo(count));
            destination.Count().Should(Be.EqualTo(prevDestinationCount + count));
        }

        [Test]
        public void ShouldInsertAll()
        {
            var prevCount = Destination.Count();
            var count = TestClassInstances.Count();
            var insertCount = Destination.BulkInsert(TestClassInstances.Select(x => new TTestClass
                                                                            {
                                                                                AnotherStringProperty = x.StringProperty,
                                                                                IntProperty = x.AnotherIntProperty,
                                                                                AnotherIntProperty = 4,
                                                                                StringProperty = "lala"
                                                                            }));
            insertCount.Should(Be.EqualTo(count));
            Destination.Count().Should(Be.EqualTo(prevCount + insertCount));
        }

        [Test]
        public void ShouldInsertAllIntoItself()
        {
            var count = TestClassInstances.Count();
            TestClassInstances.BulkInsert(TestClassInstances.Select(x => new TTestClass
                                                   {
                                                       AnotherStringProperty = x.StringProperty,
                                                       IntProperty = x.AnotherIntProperty,
                                                       AnotherIntProperty = 4,
                                                       StringProperty = "lala"
                                                   })).Should(Be.EqualTo(count));
            TestClassInstances.Count().Should(Be.EqualTo(count * 2));
        }

        [Test]
        public void ShouldInsertComplexWhere()
        {
            TestInsert(x => (x.StringProperty == "sarasa" && x.IntProperty >= 3) ||
                            x.AnotherIntProperty < 12 &&
                            x.AnotherStringProperty != null, x => new TTestClass
                                                                      {
                                                                          AnotherStringProperty = x.StringProperty,
                                                                          IntProperty = x.AnotherIntProperty,
                                                                          AnotherIntProperty = 4,
                                                                          StringProperty = "lala"
                                                                      }, Destination);
        }

        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void ShouldFailInsertToQuery()
        {
            var dest = Destination.Where(x => x.StringProperty == "sarasa");
            dest.BulkInsert(TestClassInstances
                .Select(x => new TTestClass
                {
                    AnotherStringProperty = x.StringProperty,
                    IntProperty = x.AnotherIntProperty,
                    AnotherIntProperty = 4,
                    StringProperty = "lala"
                }));
        }
        [Test]
        public void ShouldInsertGroupedByAnnonymousType()
        {
            var items = TestClassInstances.Where(x => x.AnotherIntProperty > 3)
                                   .GroupBy(x => x.AnotherStringProperty)
                                   .Select(y => new { Prop = y.Count() });

            AnotherTestClassInstances.BulkInsert(items.Select(x => new AnotherTestClass
                                                    {
                                                        Int = x.Prop
                                                    }));
        }

        [Test]
        public void ShouldInsertGroupedByValue()
        {
            var items = TestClassInstances.Where(x => x.AnotherIntProperty > 3)
                                          .GroupBy(x => x.AnotherStringProperty)
                                          .Select(y => y.Count());
            AnotherTestClassInstances.BulkInsert(items.Select(x => new AnotherTestClass
                                                                  {
                                                                      Int = x
                                                                  }));
        }

        [Test]
        public void ShouldInsertNotInheritedSubclasses()
        {
            var desTestClass2 = Configurator.GetQueryable<ReferenceTestClass>();
            var prevCount = desTestClass2.Count();
            var count = ReferenceClassInstances.Count();
            var insertCount = desTestClass2.BulkInsert(ReferenceClassInstances.Select(x => new ReferenceTestClass
                                                                              {
                                                                                  IntProperty = x.IntProperty + 2,
                                                                                  StringProperty = "lala",
                                                                              }));
            insertCount.Should(Be.EqualTo(count));
            desTestClass2.Count().Should(Be.EqualTo(prevCount + insertCount));
        }

        [Test]
        public void ShouldInsertOrderBy()
        {
            var items = TestClassInstances.Where(x => x.AnotherIntProperty > 3).OrderBy(x => x.IntProperty);
            AnotherTestClassInstances.BulkInsert(items.Select(x => new AnotherTestClass
                                                    {
                                                        Int = x.IntProperty
                                                    }));

            var sortedItems = AnotherTestClassInstances.OrderBy(x => x.Int).ToList();
            var retrievedItems = AnotherTestClassInstances.ToList();

            for (var i = 0; i < sortedItems.Count; i++)
                retrievedItems[i].Id.Should(Be.EqualTo(sortedItems[i].Id));
        }

        [Test]
        public void ShouldInsertReferences()
        {
            var prevCount = Destination.Count();
            var count = TestClassInstances.Count();
            var insertCount = Destination.BulkInsert(TestClassInstances.Select(x => new TTestClass
                                                                     {
                                                                         AnotherStringProperty = x.StringProperty,
                                                                         IntProperty = x.AnotherIntProperty,
                                                                         AnotherIntProperty = 4,
                                                                         StringProperty = "lala",
                                                                         Reference = x.Reference
                                                                     }));
            insertCount.Should(Be.EqualTo(count));
            Destination.Count().Should(Be.EqualTo(prevCount + insertCount));
        }

        [Test]
        public void ShouldInsertWhereIntoItself()
        {
            TestInsert(x => (x.StringProperty == "sarasa" && x.IntProperty >= 3) ||
                            x.AnotherIntProperty < 12 &&
                            x.AnotherStringProperty != null, x => new TTestClass
                                                                      {
                                                                          AnotherStringProperty = x.StringProperty,
                                                                          IntProperty = x.AnotherIntProperty,
                                                                          AnotherIntProperty = 4,
                                                                          StringProperty = "lala"
                                                                      }, TestClassInstances);
        }

        [Test]
        public void ShouldInsertWhereThatUsesAnotherIntProperty()
        {
            TestInsert(x => x.IntProperty == x.AnotherIntProperty, x => new TTestClass
                                                                            {
                                                                                AnotherStringProperty =
                                                                                    x.StringProperty,
                                                                                IntProperty = x.AnotherIntProperty,
                                                                                AnotherIntProperty = 4,
                                                                                StringProperty = "lala"
                                                                            }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesAnotherStringProperty()
        {
            TestInsert(x => x.StringProperty == x.AnotherStringProperty, x => new TTestClass
                                                                                  {
                                                                                      AnotherStringProperty =
                                                                                          x.StringProperty,
                                                                                      IntProperty = x.AnotherIntProperty,
                                                                                      AnotherIntProperty = 4,
                                                                                      StringProperty = "lala"
                                                                                  }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesComposedIntProperty()
        {
            TestInsert(x => x.IntProperty == x.AnotherIntProperty + 3, x => new TTestClass
                                                                                {
                                                                                    AnotherStringProperty =
                                                                                        x.StringProperty,
                                                                                    IntProperty = x.AnotherIntProperty,
                                                                                    AnotherIntProperty = 4,
                                                                                    StringProperty = "lala"
                                                                                }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesComposedStringProperty()
        {
            TestInsert(x => x.StringProperty == x.AnotherStringProperty + "la", x => new TTestClass
                                                                                         {
                                                                                             AnotherStringProperty =
                                                                                                 x.StringProperty,
                                                                                             IntProperty =
                                                                                                 x.AnotherIntProperty,
                                                                                             AnotherIntProperty = 4,
                                                                                             StringProperty = "lala"
                                                                                         }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesIntProperty()
        {
            TestInsert(x => x.IntProperty == 3, x => new TTestClass
                                                         {
                                                             AnotherStringProperty =
                                                                 x.StringProperty,
                                                             IntProperty = x.AnotherIntProperty,
                                                             AnotherIntProperty = 4,
                                                             StringProperty = "lala"
                                                         }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesNullableIntProperty()
        {
            TestInsert(x => x.NullableIntProperty == 3,
                       x =>
                       new TTestClass
                           {
                               AnotherStringProperty = x.StringProperty,
                               IntProperty = x.AnotherIntProperty,
                               AnotherIntProperty = 4,
                               StringProperty = "lala"
                           }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesNullableIntPropertyNull()
        {
            TestInsert(x => x.NullableIntProperty == null,
                       x =>
                       new TTestClass
                           {
                               AnotherStringProperty = x.StringProperty,
                               IntProperty = x.AnotherIntProperty,
                               AnotherIntProperty = 4,
                               StringProperty = "lala"
                           }, Destination);
        }


        [Test]
        public void ShouldInsertWhereThatUsesNullableIntPropertyToNull()
        {
            TestInsert(x => x.NullableIntProperty == null,
                       x =>
                       new TTestClass
                           {
                               AnotherStringProperty = x.StringProperty,
                               IntProperty = x.AnotherIntProperty,
                               AnotherIntProperty = 4,
                               StringProperty = "lala"
                           }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesNullableIntPropertyWithIntProperty()
        {
            TestInsert(x => x.NullableIntProperty == x.IntProperty,
                       x =>
                       new TTestClass
                           {
                               AnotherStringProperty = x.StringProperty,
                               IntProperty = x.AnotherIntProperty,
                               AnotherIntProperty = 4,
                               StringProperty = "lala"
                           }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesReference()
        {
            var r = ReferenceClassInstances.First();
            TestInsert(x => x.Reference != null && x.Reference.Id == r.Id, x => new TTestClass
                                                       {
                                                           AnotherStringProperty =
                                                               x.StringProperty,
                                                           IntProperty = x.AnotherIntProperty,
                                                           AnotherIntProperty = 4,
                                                           StringProperty = "lala"
                                                       }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesReferenceNull()
        {
            TestInsert(x => x.Reference == null,
                       x =>
                       new TTestClass
                           {
                               AnotherStringProperty = x.StringProperty,
                               IntProperty = x.AnotherIntProperty,
                               AnotherIntProperty = 4,
                               StringProperty = "lala"
                           }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesSelfReference()
        {
            TestInsert(x => x.SelfReference != null && x.SelfReference.IntProperty > 3,
                       x =>
                       new TTestClass
                           {
                               AnotherStringProperty = x.StringProperty,
                               IntProperty = x.AnotherIntProperty,
                               AnotherIntProperty = 4,
                               StringProperty = "lala"
                           }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesStringProperty()
        {
            TestInsert(x => x.StringProperty == "sarasa", x => new TTestClass
                                                                   {
                                                                       AnotherStringProperty = x.StringProperty,
                                                                       IntProperty = x.AnotherIntProperty,
                                                                       AnotherIntProperty = 4,
                                                                       StringProperty = "lala"
                                                                   }, Destination);
        }

        [Test]
        public void ShouldInsertWhereThatUsesStringPropertyNull()
        {
            TestInsert(x => x.StringProperty == null, x => new TTestClass
                                                               {
                                                                   AnotherStringProperty =
                                                                       x.StringProperty,
                                                                   IntProperty = x.AnotherIntProperty,
                                                                   AnotherIntProperty = 4,
                                                                   StringProperty = "lala"
                                                               }, Destination);
        }
    }
}